home *** CD-ROM | disk | FTP | other *** search
/ InfoMagic Internet Tools 1993 July / Internet Tools.iso / RockRidge / mail / smail-3.1.28 / src / parse.c < prev    next >
Encoding:
C/C++ Source or Header  |  1992-07-11  |  18.7 KB  |  774 lines

  1. /* @(#)src/parse.c    1.4 7/11/92 11:49:50 */
  2.  
  3. /*
  4.  *    Copyright (C) 1987, 1988 by Ronald S. Karr and Landon Curt Noll
  5.  *    Copyright (C) 1992  Ronald S. Karr
  6.  *
  7.  * See the file COPYING, distributed with smail, for restriction
  8.  * and warranty information.
  9.  */
  10.  
  11. /*
  12.  * parse.c:
  13.  *    Parse configuration files in a standard way.
  14.  *
  15.  *    The directory, router and transport files all share a common
  16.  *    format which are parsed using routines in this file.  Although
  17.  *    the format is different, the rules for lexical tokens in the
  18.  *    method and config files are the same, so routines for parsing
  19.  *    these files is given as well.
  20.  *
  21.  *    external functions:  read_entry, parse_entry, parse_config.
  22.  */
  23. #ifdef STANDALONE
  24. # define xmalloc malloc
  25. # define xrealloc realloc
  26. # define xfree free
  27. #endif    /* STANDALONE */
  28.  
  29. #include <stdio.h>
  30. #include <ctype.h>
  31. #include "defs.h"
  32. #include "smail.h"
  33. #include "parse.h"
  34. #include "dys.h"
  35. #include "exitcodes.h"
  36. #ifndef DEPEND
  37. # include "debug.h"
  38. # include "extern.h"
  39. #endif
  40.  
  41. /* variables exported from this file */
  42. char *on = "1";                /* boolean on attribute value */
  43. char *off = "0";            /* boolean off attribute value */
  44.  
  45. /* functions local to this file */
  46. static char *c_quote();
  47. static char *eof_error();
  48. static char *finish_parse();
  49. static char *skip_space();
  50.  
  51.  
  52. /*
  53.  * parse_entry - parse an entry from director, router or transport file
  54.  *
  55.  * given an entry from the given file, parse that entry, returning
  56.  * returning the name of the entry and name/value pairs for all of the
  57.  * attributes in the entry.  Names which fit the form of a boolean
  58.  * attribute will return a pointer to one of the external variables
  59.  * "on" or "off".
  60.  *
  61.  * Returns the name of the entry on a successful read.  Returns NULL
  62.  * on end of file or error.  For an error, a message is returned as an
  63.  * error string.  Calling xfree on the name is sufficient to free all
  64.  * of the string storage.  To free the list entries, step through the
  65.  * lists and free each in turn.
  66.  */
  67. char *
  68. parse_entry(entry, generic, driver, error)
  69.     register char *entry;        /* entry string to be parsed */
  70.     struct attribute **generic;        /* all generic attributes */
  71.     struct attribute **driver;        /* all driver attributes */
  72.     char **error;            /* returned error message */
  73. {
  74.     struct str str;            /* string for processing entry */
  75.     int this_attribute;            /* offset of attribute name */
  76.  
  77.     *error = NULL;            /* no error yet */
  78.     /*
  79.      * grab the entry name as a collection of characters followed
  80.      * by optional white space followed by a `:'
  81.      */
  82.     STR_INIT(&str);
  83.     while (*entry && !isspace(*entry) && *entry != ':' && *entry != '#') {
  84.     STR_NEXT(&str, *entry++);
  85.     }
  86.     entry = skip_space(entry);
  87.  
  88.     if (*entry != ':') {
  89.     *error = "field name does not end in `:'";
  90.     return NULL;
  91.     }
  92.     STR_NEXT(&str, '\0');        /* done with the name */
  93.     entry++;
  94.  
  95.     if (str.i == 0) {
  96.     *error = "null field name";
  97.     return NULL;
  98.     }
  99.  
  100.     /*
  101.      * loop grabbing attributes and values until the end of
  102.      * the entry
  103.      */
  104.     while (*entry) {
  105.     int i;                /* temp */
  106.  
  107.     /* attribute name begins at next non-white space character */
  108.     entry = skip_space(entry);
  109.  
  110.     if (*entry == ';') {
  111.         /* `;' separates generic and driver attributes */
  112.         entry = skip_space(entry + 1);
  113.         if (*entry) {
  114.         STR_NEXT(&str, ';');
  115.         }
  116.     }
  117.  
  118.     /* be lenient about a `;' or `,' with no following attributes */
  119.     if (*entry == '\0') {
  120.         break;
  121.     }
  122.  
  123.     /* attribute name is of the form [+-]?[A-Za-z0-9_-]+ */
  124.     this_attribute = str.i;
  125.     if (*entry == '+' || *entry == '-') {
  126.         STR_NEXT(&str, *entry++);
  127.     }
  128.     i = str.i;
  129.     while (*entry && (isalnum(*entry) || *entry == '_' || *entry == '-')) {
  130.         STR_NEXT(&str, *entry++);
  131.     }
  132.     if (i == str.i) {
  133.         *error = "null attribute name";
  134.         return NULL;
  135.     }
  136.     STR_NEXT(&str, '\0');        /* terminate the attribute name */
  137.     entry = skip_space(entry);
  138.  
  139.     if (*entry == '\0' || *entry == ',' || *entry == ';') {
  140.         /* end of boolean attribute */
  141.         if (*entry == ',') {
  142.         /* skip over commas */
  143.         entry++;
  144.         }
  145.         continue;
  146.     }
  147.     if (*entry != '=') {
  148.         /* not boolean form and not "name = value" form */
  149.         *error = xmalloc(strlen(str.p + this_attribute) +
  150.                  sizeof("no value for attribute "));
  151.         (void) sprintf(*error, "no value for attribute %s",
  152.                str.p + this_attribute);
  153.         return NULL;
  154.     }
  155.  
  156.     /* note that this is a value */
  157.     STR_NEXT(&str, '=');
  158.  
  159.     entry = skip_space(entry + 1);
  160.  
  161.     if (*entry == '"') {
  162.         entry++;
  163.         /*
  164.          * if a quote, skip to the closing quote, following standard
  165.          * C convention with \-escapes.  Note that read_entry will
  166.          * have already done some processing for \ chars at the end of
  167.          * input lines.
  168.          */
  169.         while (*entry && *entry != '"') {
  170.         if (*entry == '\\') {
  171.             int c;
  172.  
  173.             entry = c_quote(entry + 1, &c);
  174.             STR_NEXT(&str, c);
  175.         } else {
  176.             STR_NEXT(&str, *entry++);
  177.         }
  178.         }
  179.         if (*entry == '\0') {
  180.         /*
  181.          * make sure that the string doesn't suddenly come
  182.          * to an end at a funny spot
  183.          */
  184.         *error = eof_error(str.p + this_attribute);
  185.         return NULL;
  186.         }
  187.         entry++;            /* skip the " */
  188.     } else {
  189.         /*
  190.          * not in double quotes, only a limited set of characters
  191.          * are allowed in an unquoted string, though \ quotes any
  192.          * character.
  193.          */
  194.         while (*entry && (*entry == '\\' ||
  195.                   index("!@$%^&*-_+~/?|<>:[]{}.`'", *entry) ||
  196.                   isalnum(*entry)))
  197.         {
  198.         if (*entry == '\\') {
  199.             entry++;
  200.             if (*entry == '\0') {
  201.             /* must have something after \ */
  202.             *error = eof_error(str.p + this_attribute);
  203.             return NULL;
  204.             }
  205.         }
  206.         STR_NEXT(&str, *entry++);
  207.         }
  208.     }
  209.     STR_NEXT(&str, '\0');        /* close off the value */
  210.     entry = skip_space(entry);
  211.  
  212.     /*
  213.      * make sure the entry ends in something reasonable
  214.      */
  215.     if (*entry == ',') {
  216.         entry++;            /* commas are okay, and are ignored */
  217.     } else if (*entry != '\0' && *entry != ';') {
  218.         /* end of string or ; separator are okay, anything else is not */
  219.         *error = xmalloc(strlen(str.p + this_attribute) +
  220.                  sizeof("illegal attribute separator after "));
  221.         (void) sprintf(*error, "illegal attribute separator after %s",
  222.                str.p + this_attribute);
  223.         return NULL;
  224.     }
  225.     }
  226.     STR_NEXT(&str, '\0');        /* two nul bytes signal the end */
  227.     STR_DONE(&str);            /* finish off the string */
  228.  
  229.     /*
  230.      * turn all this into the finished tokens
  231.      */
  232.     *error = finish_parse(str.p + strlen(str.p) + 1, generic, driver);
  233.  
  234.     if (*error) {
  235.     return NULL;            /* error found in finish_parse */
  236.     }
  237.  
  238.     return str.p;            /* entry name was first */
  239. }
  240.  
  241. /*
  242.  * c_quote - translate a \escape as C would within a string or char literal
  243.  *
  244.  * return next character to fetch from string and the actual char in val.
  245.  */
  246. static char *
  247. c_quote(p, val)
  248.     register char *p;            /* start point in string */
  249.     register int *val;            /* put result char here */
  250. {
  251.     register int c;            /* current character */
  252.     int cnt;
  253.  
  254.     switch (c = *p++) {
  255.     case '\0':
  256.     *val = '\0';
  257.     return p - 1;
  258.     case 'b':
  259.     *val = '\b';
  260.     return p;
  261.     case 'e':
  262.     *val = '\033';
  263.     return p;
  264.     case 'f':
  265.     *val = '\f';
  266.     return p;
  267.     case 'g':
  268.     *val = '\007';
  269.     return p;
  270.     case 'n':
  271.     *val = '\n';
  272.     return p;
  273.     case 'r':
  274.     *val = '\r';
  275.     return p;
  276.     case 't':
  277.     *val = '\t';
  278.     return p;
  279.     case 'v':
  280.     *val = '\v';
  281.     return p;
  282.     case 'x':
  283.     case 'X':
  284.     /*
  285.      * x or X followed by up to three hex digits form a char
  286.      */
  287.     cnt = 3;
  288.     *val = 0;
  289.     while (cnt && (isdigit(c = *p++) ||
  290.                (c >= 'A' && c <= 'F') ||
  291.                (c >= 'a' && c <= 'f')))
  292.     {
  293.         *val = (*val * 16) +
  294.         (isdigit(c)? c - '0': isupper(c)? c - 'A': c - 'a');
  295.     }
  296.     return p - 1;
  297.     case '0': case '1': case '2': case '3':
  298.     case '4': case '5': case '6': case '7':
  299.     /* handle the normal, octal, case of chars specified numerically */
  300.     cnt = 2;            /* two more digits, three total */
  301.     *val = c - '0';
  302.     while (cnt && (c = *p++) >= '0' && c <= '7') {
  303.         *val = (*val * 8) + c - '0';
  304.     }
  305.     return p - 1;
  306.     default:
  307.     *val = c;
  308.     return p;
  309.     }
  310. }
  311.  
  312. /*
  313.  * eof_error - form an unexpected eof error on the given attribute
  314.  */
  315. static char *
  316. eof_error(name)
  317.     char *name;                /* name of attribute */
  318. {
  319.     static char *message = "unexpected end of string for attribute %s";
  320.     char *error = xmalloc(strlen(name) + sizeof(message));
  321.  
  322.     (void) sprintf(error, message, name);
  323.     return error;
  324. }
  325.  
  326. /*
  327.  * finish_parse - turn nul-separated token strings into an attribute list
  328.  *
  329.  * return an error message or NULL, return generic and driver attributes
  330.  * in the appropriate passed list pointers.
  331.  */
  332. static char *
  333. finish_parse(tokens, generic, driver)
  334.     register char *tokens;        /* strings of nul-terminated tokens */
  335.     struct attribute **generic;        /* generic attributes go here */
  336.     struct attribute **driver;        /* driver attributes go here */
  337. {
  338.     struct attribute **attr = generic;    /* begin adding generic attributes */
  339.     *generic = NULL;
  340.     *driver = NULL;
  341.  
  342.     /*
  343.      * loop, snapping up tokens until no more remain
  344.      */
  345.     while (*tokens) {
  346.     struct attribute *new;
  347.  
  348.     if (*tokens == ';') {
  349.         /* after `;' parse driver attributes */
  350.         attr = driver;
  351.         tokens++;            /* otherwise ignore `;' */
  352.     }
  353.  
  354.     /*
  355.      * get a new token and link it into the output list
  356.      */
  357.     new = (struct attribute *)xmalloc(sizeof(*new));
  358.     new->succ = *attr;
  359.     (*attr) = new;
  360.  
  361.     /* fill in the name */
  362.     new->name = tokens;
  363.     /* step to the next token */
  364.     tokens = tokens + strlen(tokens) + 1;
  365.  
  366.     /* check for boolean attribute form */
  367.     if (new->name[0] == '-' || new->name[0] == '+') {
  368.         /* boolean value */
  369.         if (*tokens == '=') {
  370.         /* can't have both [+-] and a value */
  371.         return "mixed [+-]attribute and value assignment";
  372.         }
  373.  
  374.         /*
  375.          * -name turns off attribute, +name turns it on
  376.          */
  377.         if (new->name[0] == '-') {
  378.         new->value = off;
  379.         } else {
  380.         new->value = on;
  381.         }
  382.         new->name++;        /* don't need [+-] anymore */
  383.     } else {
  384.         if (*tokens == '=') {
  385.         /* value token for attribute */
  386.         new->value = tokens + 1; /* don't include `=' in the value */
  387.         /* advance to the next token */
  388.         tokens = tokens + strlen(tokens) + 1;
  389.         } else {
  390.         /* just name is equivalent to +name */
  391.         new->value = on;
  392.         }
  393.     }
  394.     }
  395.  
  396.     return NULL;
  397. }
  398.  
  399.  
  400. /*
  401.  * parse_config - parse config file name/value pairs
  402.  *
  403.  * given a string, such as returned by read_entry, turn it into
  404.  * a single attribute entry.  On error, return NULL, with an
  405.  * error message in *error.
  406.  */
  407. struct attribute *
  408. parse_config(entry, error)
  409.     register char *entry;        /* config from read_entry */
  410.     char **error;            /* return error message */
  411. {
  412.     struct str str;            /* area for building result */
  413.     int attr_type = ' ';        /* `+' `-' or SPACE */
  414.     int value_offset;            /* offset in str.p of value */
  415.     struct attribute *attr = (struct attribute *)xmalloc(sizeof(*attr));
  416.  
  417.     attr->succ = NULL;
  418.     STR_INIT(&str);
  419.  
  420.     /* can be preceded by whitespace */
  421.     entry = skip_space(entry);
  422.  
  423.     if (*entry == '+') {
  424.     entry++;            /* skip over a leading + */
  425.     attr_type = '+';        /* positive boolean */
  426.     } else if (*entry == '-') {
  427.     entry++;            /* skip over a leading - */
  428.     attr_type = '-';        /* negative boolean */
  429.     }
  430.  
  431.     /*
  432.      * get the attribute name
  433.      */
  434.     while (*entry && (isalnum(*entry) || *entry == '_' || *entry == '-')) {
  435.     STR_NEXT(&str, *entry++);
  436.     }
  437.     STR_NEXT(&str, '\0');        /* termiante attribute name */
  438.  
  439.     entry = skip_space(entry);
  440.  
  441.     if (*entry == '\0') {
  442.     /* boolean attribute */
  443.     STR_DONE(&str);
  444.     attr->name = str.p;
  445.     if (attr_type == '-') {
  446.         attr->value = off;
  447.     } else {
  448.         attr->value = on;
  449.     }
  450.  
  451.     return attr;
  452.     } else if (*entry != '=') {
  453.     *error = "expected `=' after attribute name";
  454.     return NULL;
  455.     }
  456.  
  457.     if (attr_type != ' ') {
  458.     *error = "unexpected pattern:  `= value' follows boolean attribute";
  459.     return NULL;
  460.     }
  461.  
  462.     /* form is name = value, find the value */
  463.  
  464.     entry = skip_space(entry + 1);
  465.  
  466.     value_offset = str.i;
  467.  
  468.     if (*entry == '"') {
  469.     entry++;
  470.     /* if a quote, skip to the closing quote (\ quotes next char) */
  471.     while (*entry && *entry != '"') {
  472.         if (*entry == '\\') {
  473.         int c;
  474.  
  475.         entry = c_quote(entry + 1, &c);
  476.         STR_NEXT(&str, c);
  477.         } else {
  478.         STR_NEXT(&str, *entry++);
  479.         }
  480.     }
  481.     if (*entry == '\0') {
  482.         /*
  483.          * make sure that the string doesn't suddenly come
  484.          * to an end at a funny spot
  485.          */
  486.         *error = "unexpected end of attribute";
  487.         return NULL;
  488.     }
  489.     entry++;            /* skip the " */
  490.     } else {
  491.     /*
  492.      * not in double quotes, only a limited set of characters
  493.      * are allowed in an unquoted string, though \ quotes any
  494.      * character.
  495.      */
  496.     while (*entry && (*entry == '\\' ||
  497.               index("!@$%^&*-_+~/?|<>:[]{}.`'", *entry) ||
  498.               isalnum(*entry)))
  499.     {
  500.         if (*entry == '\\') {
  501.         entry++;
  502.         if (*entry == '\0') {
  503.             /* must have something after \ */
  504.             *error = "unexpected end of attribute";
  505.             return NULL;
  506.         }
  507.         }
  508.         STR_NEXT(&str, *entry++);
  509.     }
  510.     }
  511.     STR_NEXT(&str, '\0');        /* close off the value */
  512.     entry = skip_space(entry);
  513.  
  514.     /*
  515.      * make sure this is really the end of the entry
  516.      */
  517.     if (*entry != '\0') {
  518.     *error = "expected end of entry";
  519.     return NULL;
  520.     }
  521.  
  522.     STR_DONE(&str);
  523.  
  524.     attr->name = str.p;
  525.     attr->value = str.p + value_offset;
  526.  
  527.     return attr;
  528. }
  529.  
  530.  
  531. /*
  532.  * parse_table - parse an entry in a table file
  533.  *
  534.  * table files have entries of the form:
  535.  *
  536.  *    string1        string2
  537.  *
  538.  * returned attribute has string1 as name and string2 as value.
  539.  */
  540. struct attribute *
  541. parse_table(entry, error)
  542.     register char *entry;        /* config from read_entry */
  543.     char **error;            /* return error message */
  544. {
  545.     struct attribute *attr = (struct attribute *)xmalloc(sizeof(*attr));
  546.     struct str str;
  547.     int offset_transport;        /* offset to transport in str.p */
  548.  
  549.     attr->succ = NULL;
  550.     STR_INIT(&str);
  551.  
  552.     entry = skip_space(entry);
  553.     while (*entry && !isspace(*entry) && *entry != '#') {
  554.     STR_NEXT(&str, *entry++);
  555.     }
  556.     STR_NEXT(&str, '\0');        /* terminate name of host */
  557.  
  558.     entry = skip_space(entry);
  559.     if (*entry == '\0') {
  560.     *error = "unexpected end of entry";
  561.     STR_FREE(&str);
  562.     return NULL;
  563.     }
  564.  
  565.     offset_transport = str.i;
  566.     while (*entry && !isspace(*entry) && *entry != '#') {
  567.     STR_NEXT(&str, *entry++);
  568.     }
  569.     STR_NEXT(&str, '\0');        /* terminate name of transport */
  570.  
  571.     entry = skip_space(entry);
  572.     if (*entry) {
  573.     *error = "expected end of entry";
  574.     STR_FREE(&str);
  575.     return NULL;
  576.     }
  577.  
  578.     STR_DONE(&str);
  579.     attr->name = str.p;
  580.     attr->value = str.p + offset_transport;
  581.  
  582.     return attr;
  583. }
  584.  
  585. /*
  586.  * skip_space - skip over comments and white space
  587.  *
  588.  * a comment is a `#' up to the end of a line
  589.  */
  590. static char *
  591. skip_space(p)
  592.     register char *p;            /* current place in string */
  593. {
  594.     for (;;) {
  595.     if (*p == '#') {
  596.         /* skip over comment */
  597.         p++;
  598.         while (*p && *p != '\n') {
  599.         p++;
  600.         }
  601.     } else if (!isspace(*p)) {
  602.         /* found something that isn't white space, return it */
  603.         return p;
  604.     } else {
  605.         p++;            /* advance past the white-space char */
  606.     }
  607.     }
  608. }
  609.  
  610.  
  611. /*
  612.  * read_entry - read an entry from a file into memory
  613.  *
  614.  * a director, router or transport file entry is terminated by a line
  615.  * which does not begin with whitespace.
  616.  *
  617.  * return NULL on end of file or error.  The region return may be
  618.  * reused for subsequent return values and should be copied if it
  619.  * is to be preserved.
  620.  */
  621. char *
  622. read_entry(f)
  623.     register FILE *f;            /* input file */
  624. {
  625.     register int c;            /* current character */
  626.     static struct str str;        /* build the entry here */
  627.     static int inited = FALSE;        /* TRUE if str has been STR_INIT'd */
  628.  
  629.     /*
  630.      * scan for the beginning of an entry, which begins at the first
  631.      * non-white space, non-comment character
  632.      */
  633.     while ((c = getc(f)) != EOF && (isspace(c) || c == '#')) {
  634.     if (c == '#') {
  635.         while ((c = getc(f)) != EOF && c != '\n') ;
  636.         if (c == EOF) {
  637.         break;
  638.         }
  639.     }
  640.     }
  641.  
  642.     /*
  643.      * no entry was found
  644.      */
  645.     if (c == EOF) {
  646.     return NULL;
  647.     }
  648.  
  649.     /*
  650.      * copy characters up to the end of the entry.
  651.      * Note, that initialized once and reused.
  652.      */
  653.     if (!inited) {
  654.     inited = TRUE;
  655.     STR_INIT(&str);
  656.     } else {
  657.     str.i = 0;            /* back to the beginning */
  658.     }
  659.     STR_NEXT(&str, c);            /* copy in the first character */
  660.  
  661.     /*
  662.      * copy characters until a newline followed by non-white-space
  663.      */
  664.     while ((c = getc(f)) != EOF) {
  665.     if (c == '\n') {
  666.         STR_NEXT(&str, c);
  667.         c = getc(f);
  668.         /*
  669.          * end of file or line beginning with non-white space
  670.          * marks the end.
  671.          */
  672.         if (c == '\n' || c == '#') {
  673.         /* blank lines and comments don't count */
  674.         (void) ungetc(c, f);
  675.         continue;
  676.         }
  677.         if (c == EOF || (c != ' ' && c != '\t')) {
  678.         break;
  679.         }
  680.     }
  681.     if (c == '\\') {
  682.         /* \newline is swallowed along with following white-space */
  683.         if ((c = getc(f)) == EOF) {
  684.         break;
  685.         }
  686.         if (c == '\n') {
  687.         while ((c = getc(f)) == ' ' || c == '\t') ;
  688.         } else {
  689.         STR_NEXT(&str, '\\');
  690.         }
  691.     }
  692.     STR_NEXT(&str, c);
  693.     }
  694.  
  695.     /*
  696.      * that's the end of that entry
  697.      */
  698.     if (c != EOF) {
  699.     (void) ungetc(c, f);        /* first character for next time */
  700.     }
  701.     STR_NEXT(&str, '\0');        /* end of the entry */
  702.     return str.p;
  703. }
  704.  
  705. #ifdef STANDALONE
  706. /*
  707.  * read from standard input and write out the compiled
  708.  * entry information on the standard output
  709.  */
  710. void
  711. main(argc, argv)
  712.     int argc;                /* count of arguments */
  713.     char *argv[];            /* vector of arguments */
  714. {
  715.     char *entry;            /* entry read from stdin */
  716.     enum { config, table, other } file_type; /* type of file to look at */
  717.  
  718.     if (argc >= 2 && EQ(argv[1], "-c")) {
  719.     file_type = config;
  720.     } else if (argc >= 2 && EQ(argv[1], "-t")) {
  721.     file_type = table;
  722.     } else {
  723.     file_type = other;
  724.     }
  725.  
  726.     /*
  727.      * read entries until EOF
  728.      */
  729.     while (entry = read_entry(stdin)) {
  730.     if (file_type == config) {
  731.         char *error;
  732.         struct attribute *attr = parse_config(entry, &error);
  733.  
  734.         if (attr == NULL) {
  735.         (void) fprintf(stderr, "error in <stdin>: %s\n", error);
  736.         exit(EX_DATAERR);
  737.         }
  738.         (void) printf("%s = %s\n", attr->name, attr->value);
  739.     } else if (file_type == table) {
  740.         char *error;
  741.         struct attribute *attr = parse_table(entry, &error);
  742.  
  743.         if (attr == NULL) {
  744.         (void) fprintf(stderr, "error in <stdin>: %s\n", error);
  745.         exit(EX_DATAERR);
  746.         }
  747.         (void) printf("%s = %s\n", attr->name, attr->value);
  748.     } else {
  749.         struct attribute *generic;    /* generic attribute list */
  750.         struct attribute *driver;    /* driver attribute list */
  751.         char *error;        /* error message */
  752.         char *name = parse_entry(entry, &generic, &driver, &error);
  753.  
  754.         if (name == NULL) {
  755.         (void) fprintf(stderr, "error in <stdin>: %s\n", error);
  756.         exit(EX_DATAERR);
  757.         }
  758.         (void) printf("Entry Name:  %s:\n    Generic Attributes:\n", name);
  759.         while (generic) {
  760.         (void) printf("\t%s = %s\n", generic->name, generic->value);
  761.         generic = generic->succ;
  762.         }
  763.         (void) printf("    Driver Attributes:\n");
  764.         while (driver) {
  765.         (void) printf("\t%s = %s\n", driver->name, driver->value);
  766.         driver = driver->succ;
  767.         }
  768.     }
  769.     }
  770.  
  771.     exit(EX_OK);
  772. }
  773. #endif    /* STANDALONE */
  774.